#ifndef __VANILA_COMPILER_HH__
#define __VANILA_COMPILER_HH__

#include "vanila/baseobject.h"
#include "vanila/dsobject.h"
#include "vanila/object.h"
#include "vanila/value.h"
#include "vanila/chunk.h"
#include "vanila/token.h"
#include "vanila/scanner.h"
#include "vanila/parser.h"
#include "vanila/environment.h"
#include <string>
#include <map>

namespace vanila
{
class Compiler
{
    friend Parser;
    friend UnaryParser;
    friend BinaryParser;
    friend GroupingParser;
    friend IntegerParser;
    friend DecimalParser;
    friend LiteralParser;
    friend StringParser;
    friend VariableParser;
    friend AndParser;
    friend OrParser;
    friend CallParser;
    friend DotParser;
    friend ThisParser;
    friend SuperParser;
    friend ListParser;
    friend DictParser;
    friend IndexParser;

public:
    Compiler() noexcept {};
    ~Compiler() noexcept {};

    //! \brief init the Compiler!
    void init(const char* source) noexcept;

    //! \brief comile the source code
    ObjectFunction* compile();

private:
    //! \brief compile a experssion
    void compileExpression();

private:
    //! \brief comile a statement
    void compileStatement();

    //! \brief compile a expression statement
    void compileExpressionStatement();

    //! \brief compile a if statement
    void compileIfStatement();

    //! \brief compile a while loop
    void compileWhileStatement();

    //! \brief compile a for loop
    void compileForStatement();

private:
    //! \brief compile a declaration
    void compileDeclaration();

    //! \brief compile a class declaration
    void compileClassDeclaration();

    //! \brief compile a var declaration
    void compileVarDeclaration();

    //! \brief compile a fun declaration
    void compileFunDeclaration();

    //! \brief compile a block
    void compileBlock();

    //! \brief compile a return statement
    void compileReturnStatement();

    //! \brief compile a function object
    void compileFunction(FunctionType functionType);

    //! \brief compile a method
    void compileMethod();

    //! \brief compile a static field
    void compileField();

private:
    //! \brief synchronize
    void synchronize();

    //! \brief parse a variable name
    uint8_t registerVariableName(const char* errorMessage);

    //! \brief begin a scope
    void beginScope();

    //! \brief end a scope
    void endScope();

private:
    //! \brief current function's chunk
    Chunk* currentChunk() noexcept
    { return &(this->currentFunction()->chunk()); }

    //! \brief current function
    ObjectFunction* currentFunction() noexcept
    { return this->_currentEnvironment->function(); }

    //! \brief save the previous toke, and scann the next token
    void advanceToken();

    //! \brief check wheather current token's type is the specify one
    void consumeToken(TokenType tokenType, const char* message);

    //! \brief check wheather current token is the specify type, if so, consume it
    bool matchTokenType(TokenType tokenType);

    //! \brief check wheather current token is the specify type
    //! \param[in] tokenType specify token type
    bool checkTokenType(TokenType tokenType) noexcept
    { return this->_currentToken.type == tokenType; }

    //! \brief end a compiler
    ObjectFunction* endCompiler();

private:
    //! \brief write a bytecode to current chunk
    void emitBytecode(OpCode bytecode)
    { this->currentChunk()->writeBytecode(bytecode, this->_previousToken.line); }
    void emitByte(uint8_t byte)
    { this->currentChunk()->writeByte(byte, this->_previousToken.line); }
    void emitShort(uint16_t shortValue)
    { 
        this->emitByte(static_cast<uint8_t>(shortValue >> 8));
        this->emitByte(static_cast<uint8_t>(shortValue & 0xff));
    }

    //! \brief write a return bytecode to chunk
    void emitReturn();

    //! \brief write two bytecodes to current chunk
    void emitBytecodes(OpCode bytecode1, OpCode bytecode2)
    {
        this->emitBytecode(bytecode1);
        this->emitBytecode(bytecode2);
    }

    //! \brief write a bytecode and a byte to current chunk
    void emitBytecodeByte(OpCode bytecode, uint8_t byte)
    {
        this->emitBytecode(bytecode);
        this->emitByte(byte);
    }

    //! \brief write a constant value into chunk
    void emitConstant(const Value& value)
    { this->emitBytecodeByte(OpCode::CONSTANT, this->makeConstant(value)); }

    //! \brief write a jump instruction bytecode
    uint32_t emitJump(OpCode instruction);

    //! \brief emit a loop instruction, and fill the jump address
    void emitLoop(uint32_t loopStart);

private:
    //! \brief init the new environment
    void initEnvironment(Environment* environment);

    //! \brief create a constant in chunk's constant vector
    uint8_t makeConstant(const Value& value);

    //! \brief create a object string which contains the identifier name
    uint8_t makeIdentifierConstant(const Token& name);

    //! \brief patch the jump instruction's ip address
    void patchJump(uint32_t offset);

    //! \brief parse a argument list, and return argument count
    uint8_t compileArgumentList();

    //! \brief declare a global varibale
    void declareVariable();

    //! \brief define a global varibale
    void defineVariable(uint8_t global);

    //! \brief compile a named varibale, check is a global, local or upvalue, check it's get or set value
    void compileNamedVariable(const Token& name, bool canAssign);

    //! \brief compile a list
    uint16_t compileList();

    //! \brief compile a dict or a set
    uint16_t compileDictOrSet(OpCode& code);

private:
    //! \brief report the error
    void errorAt(const Token& token, const char* message);

    //! \brief error happend in _currentToken
    void errorAtCurrentToken(const char* message)
    { this->errorAt(this->_currentToken, message); }

    //! \brief error happend in _previousToken
    void errorAtPreviousToken(const char* message)
    { this->errorAt(this->_previousToken, message); }

private:
    Token _previousToken{};
    Token _currentToken{};
    Environment* _currentEnvironment = nullptr;
    ClassEnvironment* _currentClassEnvironment = nullptr;
    bool _hadError = false;
    bool _panicMode = false;

public:
    Environment* currentEnvironment() const noexcept
    { return this->_currentEnvironment; }
};
}

#endif